home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Ham Radio 2000
/
Ham Radio 2000.iso
/
ham2000
/
tcp_ip
/
os2
/
pmnos11s
/
pop3serv.c
< prev
next >
Wrap
Text File
|
1993-07-30
|
20KB
|
894 lines
/* POP3 Server state machine - see RFC 1225
*
* Jan 92 Erik Olson olson@phys.washington.edu
* Taken from POP2 server code in NOS 910618
* Rewritten/converted to POP3
* Feb 92 William Allen Simpson
* integrated with current work
*
* "Need-to" list: XTND XMIT (to get WinQVTnet to work)
*/
#if defined(OS2)
#define INCL_DOSFILEMGR
#define INCL_NOCOMMON
#include <os2.h>
int stat(PSZ pszPathName, PFILESTATUS3 pStatus);
#endif
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
//#include <sys/stat.h>
#if defined(UNIX) || defined(MSDOS)
#include <sys/types.h>
#endif
#if defined(__STDC__) || defined(__TURBOC__)
#include <stdarg.h>
#endif
#include <ctype.h>
#include <setjmp.h>
#include "global.h"
#include "mbuf.h"
#include "cmdparse.h"
#include "socket.h"
#include "proc.h"
#include "files.h"
#include "smtp.h"
/* ---------------- common server data structures ---------------- */
/* POP message pointer element */
struct pop_msg {
long len;
long pos;
int deleted;
struct pop_msg *next;
};
/* POP server control block */
struct pop_scb {
int socket; /* socket number for this connection */
char state; /* server state */
#define LSTN 0
#define AUTH 1
#define TRANS 2
#define UPDATE 3
#define DONE 5
char buf[TLINELEN]; /* input line buffer */
char count; /* line buffer length */
char username[64]; /* user/folder name */
FILE *wf; /* work folder file pointer */
int folder_len; /* number of msgs in current folder */
int high_num; /* highest message number accessed */
long folder_file_size; /* length of the current folder file, in bytes */
char folder_modified; /* mail folder contents modified flag */
struct pop_msg *msg; /* message database link-list */
};
#define NULLSCB (struct pop_scb *)0
/* Response messages */
static char count_rsp[] = "+OK you have %d messages\r\n",
error_rsp[] = "-ERR %s\r\n",
greeting_msg[] = "+OK %s POP3 ready\r\n",
user_rsp[] = "+OK user\r\n",
pass_rsp[] = "+OK password\r\n",
stat_rsp[] = "+OK %d %ld\r\n",
list_single_rsp[] = "+OK %d %d\r\n",
list_multi_rsp[] = "+OK %d messages (%ld octets)\r\n",
retr_rsp[] = "+OK %ld octets\r\n",
multi_end_rsp[] = ".\r\n",
msg_line[] = "%s\r\n",
dele_rsp[] = "+OK message %d deleted\r\n",
noop_rsp[] = "+OK\r\n",
last_rsp[] = "+OK %d\r\n",
signoff_msg[] = "+OK Bye, bye-bye, bye now, goodbye\r\n";
static void rrip __ARGS((char *s));
static struct pop_scb *create_scb __ARGS((void));
static void delete_scb __ARGS((struct pop_scb *scb));
static void popserv __ARGS((int s,void *unused,void *p));
static int poplogin __ARGS((char *pass,char *username));
static void pop_sm __ARGS((struct pop_scb *scb));
static int Spop = -1; /* prototype socket for service */
/* Start up POP receiver service */
int
pop3start(argc,argv,p)
int argc;
char *argv[];
void *p;
{
struct sockaddr_in lsocket;
int s;
if (Spop != -1) {
return 0;
}
psignal(Curproc,0); /* Don't keep the parser waiting */
chname(Curproc,"POP3 listener");
lsocket.sin_family = AF_INET;
lsocket.sin_addr.s_addr = INADDR_ANY;
if(argc < 2)
lsocket.sin_port = IPPORT_POP3;
else
lsocket.sin_port = atoi(argv[1]);
Spop = socket(AF_INET,SOCK_STREAM,0);
bind(Spop,(char *)&lsocket,sizeof(lsocket));
listen(Spop,1);
for (;;) {
if((s = accept(Spop,NULLCHAR,(int *)NULL)) == -1)
break; /* Service is shutting down */
/* Spawn a server */
newproc("POP3 server",2048,popserv,s,NULL,NULL,0);
}
return 0;
}
/* Shutdown POP3 service (existing connections are allowed to finish) */
int
pop3stop(argc,argv,p)
int argc;
char *argv[];
void *p;
{
close_s(Spop);
Spop = -1;
return 0;
}
static void
popserv(s,unused,p)
int s;
void *unused;
void *p;
{
struct pop_scb *scb;
sockowner(s,Curproc); /* We own it now */
log(s,"open POP3");
if((scb = create_scb()) == NULLSCB) {
tprintf(Nospace);
log(s,"close POP3 - no space");
close_s(s);
return;
}
scb->socket = s;
scb->state = AUTH;
(void) usprintf(s,greeting_msg,Hostname);
loop: if ((scb->count = recvline(s,scb->buf,TLINELEN)) == -1){
/* He closed on us */
goto quit;
}
rip(scb->buf);
if (strlen(scb->buf) == 0) /* Ignore blank cmd lines */
goto loop;
pop_sm(scb);
if (scb->state == DONE)
goto quit;
goto loop;
quit:
log(scb->socket,"close POP3");
close_s(scb->socket);
delete_scb(scb);
}
/* Create control block, initialize */
static struct
pop_scb *create_scb()
{
register struct pop_scb *scb;
if((scb = (struct pop_scb *)callocw(1,sizeof (struct pop_scb))) == NULLSCB)
return NULLSCB;
scb->username[0] = '\0';
scb->msg = NULL;
scb->wf = NULL;
scb->count = scb->folder_file_size = 0;
scb->folder_modified = FALSE;
return scb;
}
/* Free msg link-list */
static void
delete_msglist(struct pop_msg *b_msg)
{
struct pop_msg *msg,*msg2;
msg=b_msg;
while(msg!=NULL) {msg2=msg->next; free(msg); msg=msg2;}
}
/* Free resources, delete control block */
static void
delete_scb(scb)
register struct pop_scb *scb;
{
if (scb == NULLSCB)
return;
if (scb->wf != NULL)
fclose(scb->wf);
if (scb->msg != NULL)
delete_msglist(scb->msg);
free((char *)scb);
}
/* replace terminating end of line marker(s) (\r and \n) with null,
and change . to .. */
static void
rrip(s)
register char *s;
{
register char *cp;
if((cp = strchr(s,'\r')) != NULLCHAR)
*cp = '\0';
if((cp = strchr(s,'\n')) != NULLCHAR)
*cp = '\0';
}
/* --------------------- start of POP server code ------------------------ */
#define BITS_PER_WORD 16
#define isSOM(x) ((strncmp(x,"From ",5) == 0))
/* Command string specifications */
static char
user_cmd[] = "USER ",
pass_cmd[] = "PASS ",
quit_cmd[] = "QUIT",
stat_cmd[] = "STAT",
list_cmd[] = "LIST",
retr_cmd[] = "RETR",
dele_cmd[] = "DELE",
noop_cmd[] = "NOOP",
rset_cmd[] = "RSET",
top_cmd[] = "TOP",
last_cmd[] = "LAST";
static void
pop_sm(scb)
struct pop_scb *scb;
{
char password[40];
void state_error(struct pop_scb *,char *);
void fatal_error(struct pop_scb *,char *);
void open_folder(struct pop_scb *);
void do_cleanup(struct pop_scb *);
void stat_message(struct pop_scb *);
void list_message(struct pop_scb *);
void retr_message(struct pop_scb *);
void dele_message(struct pop_scb *);
void noop_message(struct pop_scb *);
void last_message(struct pop_scb *);
void rset_message(struct pop_scb *);
void top_message(struct pop_scb *);
void close_folder(struct pop_scb *);
if (scb == NULLSCB) /* be certain it is good -- wa6smn */
return;
switch(scb->state) {
case AUTH:
if (strncmp(scb->buf,user_cmd,strlen(user_cmd)) == 0){
sscanf(scb->buf,"USER %s",scb->username);
(void) usprintf(scb->socket,user_rsp);
} else if (strncmp(scb->buf,pass_cmd,strlen(pass_cmd)) == 0){
sscanf(scb->buf,"PASS %s",password);
if (!poplogin(scb->username,password)) {
log(scb->socket,"POP3 access DENIED to %s",
scb->username);
state_error(scb,"Access DENIED!!");
return;
}
log(scb->socket,"POP3 access granted to %s",
scb->username);
open_folder(scb);
} else if (strncmp(scb->buf,quit_cmd,strlen(quit_cmd)) == 0){
do_cleanup(scb);
} else
state_error(scb,"(AUTH) expected USER, PASS or QUIT");
break;
case TRANS:
if (strncmp(scb->buf,stat_cmd,strlen(stat_cmd)) == 0)
stat_message(scb);
else if (strncmp(scb->buf,list_cmd,strlen(list_cmd)) == 0)
list_message(scb);
else if (strncmp(scb->buf,retr_cmd,strlen(retr_cmd)) == 0)
retr_message(scb);
else if (strncmp(scb->buf,dele_cmd,strlen(dele_cmd)) == 0)
dele_message(scb);
else if (strncmp(scb->buf,last_cmd,strlen(noop_cmd)) == 0)
noop_message(scb);
else if (strncmp(scb->buf,last_cmd,strlen(last_cmd)) == 0)
last_message(scb);
else if (strncmp(scb->buf,top_cmd,strlen(top_cmd)) == 0)
top_message(scb);
else if (strncmp(scb->buf,rset_cmd,strlen(rset_cmd)) == 0)
rset_message(scb);
else if (strncmp(scb->buf,quit_cmd,strlen(quit_cmd)) == 0)
do_cleanup(scb);
else
state_error(scb,
"(TRANS) unsupported/unrecognized command");
break;
case DONE:
break;
default:
fatal_error(scb,"(TOP) State Error!!");
break;
}
}
static void
do_cleanup(scb)
struct pop_scb *scb;
{
void close_folder(struct pop_scb *);
close_folder(scb);
(void) usprintf(scb->socket,signoff_msg);
scb->state = DONE;
}
static void
state_error(scb,msg)
struct pop_scb *scb;
char *msg;
{
(void) usprintf(scb->socket,error_rsp,msg);
/* scb->state = DONE; */ /* Don't automatically hang up */
}
static void
fatal_error(scb,msg)
struct pop_scb *scb;
char *msg;
{
(void) usprintf(scb->socket,error_rsp,msg);
scb->state = DONE;
}
static void
close_folder(scb)
struct pop_scb *scb;
{
char folder_pathname[64];
char line[TLINELEN];
FILE *fd;
int deleted = FALSE;
int msg_no = 0;
struct pop_msg *msg;
int newmail(struct pop_scb *);
void state_error(struct pop_scb *,char *);
void fatal_error(struct pop_scb *,char *);
if (scb->wf == NULL)
return;
if (!scb->folder_modified) {
/* no need to re-write the folder if we have not modified it */
fclose(scb->wf);
scb->wf = NULL;
delete_msglist(scb->msg);
scb->msg=NULL;
return;
}
sprintf(folder_pathname,"%s/%s.txt",Mailspool,scb->username);
if (newmail(scb)) {
/* copy new mail into the work file and save the
message count for later */
if ((fd = fopen(folder_pathname,"r")) == NULL) {
fatal_error(scb,"Unable to add new mail to folder");
return;
}
fseek(scb->wf,0,SEEK_END);
fseek(fd,scb->folder_file_size,SEEK_SET);
while (!feof(fd)) {
fgets(line,TLINELEN,fd);
fputs(line,scb->wf);
}
fclose(fd);
}
/* now create the updated mail folder */
if ((fd = fopen(folder_pathname,"w")) == NULL){
fatal_error(scb,"Unable to update mail folder");
return;
}
rewind(scb->wf);
msg=scb->msg;
while (!feof(scb->wf)){
fgets(line,TLINELEN,scb->wf);
if (isSOM(line)){
if (msg!=NULL) msg=msg->next;
msg_no++;
if (msg!=NULL)
deleted = msg->deleted;
else
deleted = FALSE;
}
if (deleted)
continue;
fputs(line,fd);
}
fclose(fd);
fclose(scb->wf);
scb->wf = NULL;
delete_msglist(scb->msg);
scb->msg=NULL;
}
static void
open_folder(scb)
struct pop_scb *scb;
{
#if !defined(OS2)
char folder_pathname[64];
#else
char folder_pathname[256];
#endif
char line[TLINELEN];
long pos;
FILE *fd;
FILE *tmpfile();
#if !defined(OS2)
struct stat folder_stat;
#else
FILESTATUS3 folder_stat;
#endif
struct pop_msg *msg;
sprintf(folder_pathname,"%s/%s.txt",Mailspool,scb->username);
scb->folder_len = 0;
scb->folder_file_size = 0;
if (stat(folder_pathname,&folder_stat)){
(void) usprintf(scb->socket,count_rsp,scb->folder_len);
scb->state = TRANS;
return; /* no file = OK */
}
#if !defined(OS2)
scb->folder_file_size = folder_stat.st_size;
#else
scb->folder_file_size = folder_stat.cbFile;
#endif
if ((fd = fopen(folder_pathname,"r")) == NULL){
state_error(scb,"Unable to open mail folder");
return;
}
if ((scb->wf = tmpfile()) == NULL) {
state_error(scb,"Unable to create work folder");
return;
}
scb->msg=calloc(sizeof(struct pop_msg),1); /* create first element */
if (scb->msg==NULL)
{
fatal_error(scb,"Unable to create pointer list");
return;
}
scb->msg->next=NULL;
msg=scb->msg;
msg->len=0;
msg->deleted=0;
while(!feof(fd)) {
pos=ftell(scb->wf);
fgets(line,TLINELEN,fd);
/* scan for begining of a message */
if (isSOM(line))
{
scb->folder_len++;
msg->next=calloc(sizeof(struct pop_msg),1);
if (msg->next==NULL)
{
fatal_error(scb,
"Unable to create pointer list");
return;
}
msg=msg->next;
msg->pos=pos;
msg->next=NULL;
msg->len=0;
msg->deleted=0;
/* now put the line in the work file */
}
fputs(line,scb->wf);
rrip(line);
if ( *line == '.' ) msg->len++;
msg->len +=strlen(line)+2; /* Add msg len count */
}
fclose(fd);
scb->high_num=0; /* reset high read */
(void) usprintf(scb->socket,count_rsp,scb->folder_len);
scb->state = TRANS;
}
static void
stat_message(scb)
struct pop_scb *scb;
{
long total=0;
int count=0;
struct pop_msg *msg;
if (scb == NULLSCB) /* check for null -- wa6smn */
return;
if (scb->folder_len) /* add everything up */
for (msg=scb->msg->next; msg!=NULL; msg=msg->next)
if (!msg->deleted)
{ total += msg->len; ++count;}
(void) usprintf(scb->socket,stat_rsp,count,total);
}
static void
list_message(scb)
struct pop_scb *scb;
{
struct pop_msg *msg;
int msg_no=0;
long total=0;
struct pop_msg *goto_msg(struct pop_scb *,int );
if (scb == NULLSCB) /* check for null -- wa6smn */
return;
if (scb->buf[sizeof(list_cmd) - 1] == ' ')
{
msg_no = atoi(&(scb->buf[sizeof(list_cmd) - 1]));
msg=goto_msg(scb,msg_no);
if (msg==NULL || msg->deleted)
state_error(scb,"non existent or deleted message");
else
(void) usprintf(scb->socket,list_single_rsp,
msg_no,msg->len);
} else /* multiline */
{
if (scb->folder_len) /* add everything */
for (msg=scb->msg->next; msg!=NULL;msg=msg->next)
if (!msg->deleted)
total += msg->len,++msg_no;
(void) usprintf(scb->socket,list_multi_rsp,
msg_no,total);
if (scb->folder_len)
for (msg=scb->msg->next,msg_no=1; msg!=NULL;
msg=msg->next,msg_no++)
if (!msg->deleted) {
(void) usprintf(scb->socket,"%d %ld\r\n",
msg_no,msg->len);
}
(void) usprintf(scb->socket,multi_end_rsp);
}
}
static void
retr_message(scb)
struct pop_scb *scb;
{
char line[TLINELEN];
long cnt;
int msg_no;
struct pop_msg *msg;
struct pop_msg *goto_msg(struct pop_scb *,int );
if (scb == NULLSCB) /* check for null -- wa6smn */
return;
if (scb->buf[sizeof(retr_cmd) - 1] != ' ')
{
state_error(scb,"no such message");
return;
}
msg_no = atoi(&(scb->buf[sizeof(retr_cmd) - 1]));
msg=goto_msg(scb,msg_no);
if (msg==NULL || msg->deleted) {
state_error(scb,"no such message");
return;
}
cnt = msg->len;
(void) usprintf(scb->socket,retr_rsp,cnt);
fseek(scb->wf,msg->pos,SEEK_SET); /* Go there */
while(!feof(scb->wf) && (cnt > 0)) {
fgets(line,TLINELEN,scb->wf);
rrip(line);
if ( *line == '.' ) {
(void) usprintf(scb->socket,".");
cnt--;
}
(void) usprintf(scb->socket,msg_line,line);
cnt -= (strlen(line)+2); /* Compensate for CRLF */
}
(void) usprintf(scb->socket,".\r\n");
if (msg_no >= scb->high_num)
scb->high_num=msg_no; /* bump high water mark */
}
static void
noop_message(scb)
struct pop_scb *scb;
{
(void) usprintf(scb->socket,noop_rsp);
}
static void
last_message(scb)
struct pop_scb *scb;
{
(void) usprintf(scb->socket,last_rsp,scb->high_num);
}
static void
rset_message(scb)
struct pop_scb *scb;
{
struct pop_msg *msg;
long total=0;
if (scb->folder_len)
for (msg=scb->msg->next; msg!=NULL; msg=msg->next)
msg->deleted=FALSE,total+=msg->len;
scb->high_num=0; /* reset last */
scb->folder_modified=FALSE;
(void) usprintf(scb->socket,list_multi_rsp,scb->folder_len,total);
}
static void
top_message(scb)
struct pop_scb *scb;
{
char *ptr;
char line[TLINELEN];
struct pop_msg *msg;
int msg_no=0,lines=0;
long total=0;
struct pop_msg *goto_msg(struct pop_scb *,int );
if (scb == NULLSCB) /* check for null -- wa6smn */
return;
if (scb->buf[sizeof(top_cmd) - 1] != ' ')
{
state_error(scb,"No message specified");
return;
}
for (ptr=scb->buf+sizeof(top_cmd); *ptr==' ' ; ++ptr);
/* Space drop */
for ( ; *ptr!=' ' && *ptr !='\0'; ++ptr);
/* token drop */
msg_no = atoi(&(scb->buf[sizeof(top_cmd) - 1]));
lines = atoi(++ptr); /* Get # lines to top */
if (lines < 0) lines=0;
msg=goto_msg(scb,msg_no);
if (msg==NULL || msg->deleted)
{
state_error(scb,"non existent or deleted message");
return;
}
fseek(scb->wf,msg->pos,SEEK_SET); /* Go there */
total=msg->len; /* Length of current message */
(void) usprintf(scb->socket,noop_rsp); /* Give OK */
do {
fgets(line,TLINELEN,scb->wf);
rrip(line);
if ( *line == '.' ) {
(void) usprintf(scb->socket,".");
total--;
}
total -= strlen(line)+2;
(void) usprintf(scb->socket,msg_line,line);
} while (*line!='\0' && total>0);
for ( ; total > 0 && lines; --lines) {
fgets(line,TLINELEN,scb->wf);
rrip(line);
if ( *line == '.' ) {
(void) usprintf(scb->socket,".");
total--;
}
total -= strlen(line)+2;
(void) usprintf(scb->socket,msg_line,line);
}
(void) usprintf(scb->socket,multi_end_rsp);
}
static int
poplogin(username,pass)
char *pass;
char *username;
{
char buf[80];
char *cp;
char *cp1;
FILE *fp;
if((fp = fopen(Popusers,"r")) == NULLFILE) {
/* User file doesn't exist */
tprintf("POP users file %s not found\n",Popusers);
return(FALSE);
}
while(fgets(buf,sizeof(buf),fp),!feof(fp)) {
if(buf[0] == '#')
continue; /* Comment */
if((cp = strchr(buf,':')) == NULLCHAR)
/* Bogus entry */
continue;
*cp++ = '\0'; /* Now points to password */
if(strcmp(username,buf) == 0)
break; /* Found user name */
}
if(feof(fp)) {
/* User name not found in file */
fclose(fp);
return(FALSE);
}
fclose(fp);
if ((cp1 = strchr(cp,':')) == NULLCHAR)
return(FALSE);
*cp1 = '\0';
if(strcmp(cp,pass) != 0) {
/* Password required, but wrong one given */
return(FALSE);
}
/* whew! finally made it!! */
return(TRUE);
}
static void
dele_message(scb)
struct pop_scb *scb;
{
struct pop_msg *msg;
int msg_no;
struct pop_msg *goto_msg(struct pop_scb *,int );
if (scb == NULLSCB) /* check for null -- wa6smn */
return;
if (scb->buf[sizeof(retr_cmd) - 1] != ' ')
{
state_error(scb,"no such message");
return;
}
msg_no = atoi(&(scb->buf[sizeof(retr_cmd) - 1]));
msg=goto_msg(scb,msg_no);
if (msg==NULL || msg->deleted) {
state_error(scb,"attempt to access deleted message");
return;
}
if (msg->deleted) /* Don't bother if already dead */
{
state_error(scb,"message already deleted");
return;
}
msg->deleted=TRUE;
scb->folder_modified = TRUE;
(void) usprintf(scb->socket,dele_rsp,msg_no);
}
static int
newmail(scb)
struct pop_scb *scb;
{
#if !defined(OS2)
char folder_pathname[64];
struct stat folder_stat;
#else
char folder_pathname[256];
FILESTATUS3 folder_stat;
#endif
sprintf(folder_pathname,"%s/%s.txt",Mailspool,scb->username);
if (stat(folder_pathname,&folder_stat)) {
state_error(scb,"Unable to get old mail folder's status");
return(FALSE);
} else
#if !defined(OS2)
return ((folder_stat.st_size > scb->folder_file_size)? TRUE:FALSE);
#else
return ((folder_stat.cbFile > scb->folder_file_size)? TRUE:FALSE);
#endif
}
static struct pop_msg *
goto_msg(struct pop_scb *scb,int msg_no)
{
int msg_num;
struct pop_msg *msg;
msg_num=msg_no-1;
if (scb->folder_len==0 || msg_num < 0)
return NULL;
for (msg=scb->msg->next; msg_num && msg!=NULL; --msg_num) msg=msg->next;
return msg;
}